{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "_jQ1tEQCxwRx"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "cellView": "form",
        "colab": {},
        "colab_type": "code",
        "id": "V_sgB_5dx1f1"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "rF2x3qooyBTI"
      },
      "source": [
        "# Deep Convolutional Generative Adversarial Network"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "0TD5ZrvEMbhZ"
      },
      "source": [
        "\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n",
        "  \u003ctd\u003e\n",
        "    \u003ca target=\"_blank\" href=\"https://www.tensorflow.org/alpha/tutorials/generative/dcgan.ipynb\"\u003e\n",
        "    \u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003e\n",
        "    View on TensorFlow.org\u003c/a\u003e\n",
        "  \u003c/td\u003e\n",
        "  \u003ctd\u003e\n",
        "    \u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs/blob/master/site/en/r2/tutorials/generative/dcgan.ipynb\"\u003e\n",
        "    \u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" /\u003e\n",
        "    Run in Google Colab\u003c/a\u003e\n",
        "  \u003c/td\u003e\n",
        "  \u003ctd\u003e\n",
        "    \u003ca target=\"_blank\" href=\"https://github.com/tensorflow/docs/blob/master/site/en/r2/tutorials/generative/dcgan.ipynb\"\u003e\n",
        "    \u003cimg src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" /\u003e\n",
        "    View source on GitHub\u003c/a\u003e\n",
        "  \u003c/td\u003e\n",
        "\u003c/table\u003e"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "ITZuApL56Mny"
      },
      "source": [
        "This tutorial demonstrates how to generate images of handwritten digits using a [Deep Convolutional Generative Adversarial Network](https://arxiv.org/pdf/1511.06434.pdf) (DCGAN). The code is written using the [Keras Sequential API](https://www.tensorflow.org/guide/keras) with a `tf.GradientTape` training loop."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "2MbKJY38Puy9"
      },
      "source": [
        "## What are GANs?\n",
        "[Generative Adversarial Networks](https://arxiv.org/abs/1406.2661) (GANs) are one of the most interesting ideas in computer science today. Two models are trained simultaneously by an adversarial process. A *generator* (\"the artist\") learns to create images that look real, while a *discriminator* (\"the art critic\") learns to tell real images apart from fakes.\n",
        "\n",
        "![A diagram of a generator and discriminator](https://tensorflow.org/alpha/tutorials/generative/images/gan1.png)\n",
        "\n",
        "During training, the *generator* progressively becomes better at creating images that look real, while the *discriminator* becomes better at telling them apart. The process reaches equilibrium when the *discriminator* can no longer distinguish real images from fakes.\n",
        "\n",
        "![A second diagram of a generator and discriminator](https://tensorflow.org/alpha/tutorials/generative/images/gan2.png)\n",
        "\n",
        "This notebook demonstrate this process on the MNIST dataset. The following animation shows a series of images produced by the *generator* as it was trained for 50 epochs. The images begin as random noise, and increasingly resemble hand written digits over time.\n",
        "\n",
        "![sample output](https://tensorflow.org/images/gan/dcgan.gif)\n",
        "\n",
        "To learn more about GANs, we recommend MIT's [Intro to Deep Learning](http://introtodeeplearning.com/) course."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "e1_Y75QXJS6h"
      },
      "source": [
        "### Import TensorFlow and other libraries"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "J5oue0oqCkZZ"
      },
      "outputs": [],
      "source": [
        "from __future__ import absolute_import, division, print_function, unicode_literals"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 428
        },
        "colab_type": "code",
        "executionInfo": {
          "elapsed": 40677,
          "status": "ok",
          "timestamp": 1554225081967,
          "user_tz": 420
        },
        "id": "g5RstiiB8V-z",
        "outputId": "74cde9cf-239e-4e62-c7fd-0218c59e1d1a"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Collecting tensorflow-gpu==2.0.0-alpha0\n",
            "  Downloading https://files.pythonhosted.org/packages/1a/66/32cffad095253219d53f6b6c2a436637bbe45ac4e7be0244557210dc3918/tensorflow_gpu-2.0.0a0-cp36-cp36m-manylinux1_x86_64.whl (332.1MB)\n",
            "Successfully installed tensorflow-gpu-2.0.0a0\n"
          ]
        }
      ],
      "source": [
        "!pip install tensorflow-gpu==2.0.0-alpha0\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "WZKbyU2-AiY-"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "colab_type": "code",
        "executionInfo": {
          "elapsed": 41826,
          "status": "ok",
          "timestamp": 1554225083141,
          
          "user_tz": 420
        },
        "id": "wx-zNbLqB4K8",
        "outputId": "471ecaf1-a318-46ad-d4ee-d393403efadc"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "'2.0.0-alpha0'"
            ]
          },
          "execution_count": 5,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "tf.__version__"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 85
        },
        "colab_type": "code",
        "executionInfo": {
          "elapsed": 44426,
          "status": "ok",
          "timestamp": 1554225085752,
          
          "user_tz": 420
        },
        "id": "YzTlj4YdCip_",
        "outputId": "aa2e380a-a135-41c1-930b-c791893b522a"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Requirement already satisfied: imageio in /usr/local/lib/python3.6/dist-packages (2.4.1)\n",
            "Requirement already satisfied: numpy in /usr/local/lib/python3.6/dist-packages (from imageio) (1.14.6)\n",
            "Requirement already satisfied: pillow in /usr/local/lib/python3.6/dist-packages (from imageio) (4.1.1)\n",
            "Requirement already satisfied: olefile in /usr/local/lib/python3.6/dist-packages (from pillow-\u003eimageio) (0.46)\n"
          ]
        }
      ],
      "source": [
        "# To generate GIFs\n",
        "!pip install imageio"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "YfIk2es3hJEd"
      },
      "outputs": [],
      "source": [
        "import glob\n",
        "import imageio\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import os\n",
        "import PIL\n",
        "from tensorflow.keras import layers\n",
        "import time\n",
        "\n",
        "from IPython import display"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "iYn4MdZnKCey"
      },
      "source": [
        "### Load and prepare the dataset\n",
        "\n",
        "You will use the MNIST dataset to train the generator and the discriminator. The generator will generate handwritten digits resembling the MNIST data."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "a4fYMGxGhrna"
      },
      "outputs": [],
      "source": [
        "(train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "NFC2ghIdiZYE"
      },
      "outputs": [],
      "source": [
        "train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')\n",
        "train_images = (train_images - 127.5) / 127.5 # Normalize the images to [-1, 1]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "S4PIDhoDLbsZ"
      },
      "outputs": [],
      "source": [
        "BUFFER_SIZE = 60000\n",
        "BATCH_SIZE = 256"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "-yKCCQOoJ7cn"
      },
      "outputs": [],
      "source": [
        "# Batch and shuffle the data\n",
        "train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "THY-sZMiQ4UV"
      },
      "source": [
        "## Create the models\n",
        "\n",
        "Both the generator and discriminator are defined using the [Keras Sequential API](https://www.tensorflow.org/guide/keras#sequential_model)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "-tEyxE-GMC48"
      },
      "source": [
        "### The Generator\n",
        "\n",
        "The generator uses `tf.keras.layers.Conv2DTranspose` (upsampling) layers to produce an image from a seed (random noise). Start with a `Dense` layer that takes this seed as input, then upsample several times until you reach the desired image size of 28x28x1. Notice the `tf.keras.layers.LeakyReLU` activation for each layer, except the output layer which uses tanh."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "6bpTcDqoLWjY"
      },
      "outputs": [],
      "source": [
        "def make_generator_model():\n",
        "    model = tf.keras.Sequential()\n",
        "    model.add(layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))\n",
        "    model.add(layers.BatchNormalization())\n",
        "    model.add(layers.LeakyReLU())\n",
        "\n",
        "    model.add(layers.Reshape((7, 7, 256)))\n",
        "    assert model.output_shape == (None, 7, 7, 256) # Note: None is the batch size\n",
        "\n",
        "    model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False))\n",
        "    assert model.output_shape == (None, 7, 7, 128)\n",
        "    model.add(layers.BatchNormalization())\n",
        "    model.add(layers.LeakyReLU())\n",
        "\n",
        "    model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))\n",
        "    assert model.output_shape == (None, 14, 14, 64)\n",
        "    model.add(layers.BatchNormalization())\n",
        "    model.add(layers.LeakyReLU())\n",
        "\n",
        "    model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))\n",
        "    assert model.output_shape == (None, 28, 28, 1)\n",
        "\n",
        "    return model"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "GyWgG09LCSJl"
      },
      "source": [
        "Use the (as yet untrained) generator to create an image."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 286
        },
        "colab_type": "code",
        "executionInfo": {
          "elapsed": 49698,
          "status": "ok",
          "timestamp": 1554225091093,
          
          "user_tz": 420
        },
        "id": "gl7jcC7TdPTG",
        "outputId": "6368572a-2cb9-4537-89a3-15294dc5ee7e"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "\u003cmatplotlib.image.AxesImage at 0x7f18c841c6d8\u003e"
            ]
          },
          "execution_count": 13,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        },
        {
          "data": {
            "image/png": "iVBORw0KGgoAAAANSUhEUgAAAP8AAAD8CAYAAAC4nHJkAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAGMlJREFUeJzt3Wtw1dW5BvDnJVwM96sQIVyLeAEL\nGhAKtXgtWDrY2lK1OuhQsB1obXs61fF0evxgp45KW6djO8WDSB0t6gAWp6hUsCpUkItyVa6CgEkA\nQQjILeQ9H7LtRGU9KyZh7+1Zz2+GIeTJm72ys1/2TtZ/rWXuDhFJT6NcD0BEckPNL5IoNb9IotT8\nIolS84skSs0vkig1v0ii1PwiiVLziySqcTZvrHnz5t6mTZs61zdqFP6/ysxo7cmTJ2leUFBQpzEB\nQJMmTep12+zrqk09+9pj90tMVVUVzRs35g8hNvbY1x0be+zq1Pp87bHPHfu6Y9j9Wp/b3r9/P44c\nOVKrL7xeX4GZjQLwEIACAP/r7vexj2/Tpg1uu+22YB77ogsLC4NZs2bNaG15eTnNW7VqRXM2ti5d\nutTrtlu2bEnz0tJSmrMHQ+xBGrvPjx8/TvO2bdvSfM+ePcGsRYsWtDbWvPX9j4mJ/YfbqVOnOn9u\nADh69GgwO3bsGK3t0KFDMHvooYdqPYY6v+w3swIADwMYDeACADea2QV1/Xwikl31+Zl/CIAt7r7N\n3U8AmAVgbMMMS0TOtPo0f1cAO2v8e1fmfZ9gZpPMbIWZrfjoo4/qcXMi0pDO+G/73X2au5e4e0nz\n5s3P9M2JSC3Vp/l3Ayiu8e9umfeJyBdAfZp/OYC+ZtbLzJoCuAHAvIYZloicaXWeC3H3SjObAuBF\nVE/1Peru61mNmdH59Nh0G5sCiU1ZDR06lObt2rWj+fz584NZUVERrY1Nl/Xq1YvmxcXFNN++fXsw\n69r1M7+G+YQ+ffrQfPdu/mIu9nucjh07BrPLLruM1m7ZsoXmW7dupXlZWVkwu+ACPjG1YcMGmm/b\nto3mw4cPp/nhw4frlMVuO/ZYq6le8/zuPh9AuCtEJG/p8l6RRKn5RRKl5hdJlJpfJFFqfpFEqflF\nEpXV9fwAX6Z54MABWtu+fftgFpvf3LhxI81j8+Hstj/44ANae+LECZq/++67NI/tF8CWBLO5bgBY\n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\nAABJRU5ErkJggg==\n",
            "text/plain": [
              "\u003cFigure size 432x288 with 1 Axes\u003e"
            ]
          },
          "metadata": {
            "tags": []
          },
          "output_type": "display_data"
        }
      ],
      "source": [
        "generator = make_generator_model()\n",
        "\n",
        "noise = tf.random.normal([1, 100])\n",
        "generated_image = generator(noise, training=False)\n",
        "\n",
        "plt.imshow(generated_image[0, :, :, 0], cmap='gray')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "D0IKnaCtg6WE"
      },
      "source": [
        "### The Discriminator\n",
        "\n",
        "The discriminator is a CNN-based image classifier."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "dw2tPLmk2pEP"
      },
      "outputs": [],
      "source": [
        "def make_discriminator_model():\n",
        "    model = tf.keras.Sequential()\n",
        "    model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same',\n",
        "                                     input_shape=[28, 28, 1]))\n",
        "    model.add(layers.LeakyReLU())\n",
        "    model.add(layers.Dropout(0.3))\n",
        "\n",
        "    model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))\n",
        "    model.add(layers.LeakyReLU())\n",
        "    model.add(layers.Dropout(0.3))\n",
        "\n",
        "    model.add(layers.Flatten())\n",
        "    model.add(layers.Dense(1))\n",
        "\n",
        "    return model"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "QhPneagzCaQv"
      },
      "source": [
        "Use the (as yet untrained) discriminator to classify the generated images as real or fake. The model will be trained to output positive values for real images, and negative values for fake images."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "colab_type": "code",
        "executionInfo": {
          "elapsed": 50156,
          "status": "ok",
          "timestamp": 1554225091568,
          
          "user_tz": 420
        },
        "id": "gDkA05NE6QMs",
        "outputId": "b64c1639-21a4-4f6c-9e97-b51610efa8ba"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "tf.Tensor([[-0.00070298]], shape=(1, 1), dtype=float32)\n"
          ]
        }
      ],
      "source": [
        "discriminator = make_discriminator_model()\n",
        "decision = discriminator(generated_image)\n",
        "print (decision)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "0FMYgY_mPfTi"
      },
      "source": [
        "## Define the loss and optimizers\n",
        "\n",
        "Define loss functions and optimizers for both models.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "psQfmXxYKU3X"
      },
      "outputs": [],
      "source": [
        "# This method returns a helper function to compute cross entropy loss\n",
        "cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "PKY_iPSPNWoj"
      },
      "source": [
        "### Discriminator loss\n",
        "\n",
        "This method quantifies how well the discriminator is able to distinguish real images from fakes. It compares the discriminator's predictions on real images to an array of 1s, and the discriminator's predictions on fake (generated) images to an array of 0s."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "wkMNfBWlT-PV"
      },
      "outputs": [],
      "source": [
        "def discriminator_loss(real_output, fake_output):\n",
        "    real_loss = cross_entropy(tf.ones_like(real_output), real_output)\n",
        "    fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)\n",
        "    total_loss = real_loss + fake_loss\n",
        "    return total_loss"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Jd-3GCUEiKtv"
      },
      "source": [
        "### Generator loss\n",
        "The generator's loss quantifies how well it was able to trick the discriminator. Intuitively, if the generator is performing well, the discriminator will classify the fake images as real (or 1). Here, we will compare the discriminators decisions on the generated images to an array of 1s."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "90BIcCKcDMxz"
      },
      "outputs": [],
      "source": [
        "def generator_loss(fake_output):\n",
        "    return cross_entropy(tf.ones_like(fake_output), fake_output)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "MgIc7i0th_Iu"
      },
      "source": [
        "The discriminator and the generator optimizers are different since we will train two networks separately."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "iWCn_PVdEJZ7"
      },
      "outputs": [],
      "source": [
        "generator_optimizer = tf.keras.optimizers.Adam(1e-4)\n",
        "discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "mWtinsGDPJlV"
      },
      "source": [
        "### Save checkpoints\n",
        "This notebook also demonstrates how to save and restore models, which can be helpful in case a long running training task is interrupted."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "CA1w-7s2POEy"
      },
      "outputs": [],
      "source": [
        "checkpoint_dir = './training_checkpoints'\n",
        "checkpoint_prefix = os.path.join(checkpoint_dir, \"ckpt\")\n",
        "checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,\n",
        "                                 discriminator_optimizer=discriminator_optimizer,\n",
        "                                 generator=generator,\n",
        "                                 discriminator=discriminator)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Rw1fkAczTQYh"
      },
      "source": [
        "## Define the training loop\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "NS2GWywBbAWo"
      },
      "outputs": [],
      "source": [
        "EPOCHS = 50\n",
        "noise_dim = 100\n",
        "num_examples_to_generate = 16\n",
        "\n",
        "# We will reuse this seed overtime (so it's easier)\n",
        "# to visualize progress in the animated GIF)\n",
        "seed = tf.random.normal([num_examples_to_generate, noise_dim])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "jylSonrqSWfi"
      },
      "source": [
        "The training loop begins with generator receiving a random seed as input. That seed is used to produce an image. The discriminator is then used to classify real images (drawn from the training set) and fakes images (produced by the generator). The loss is calculated for each of these models, and the gradients are used to update the generator and discriminator."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "3t5ibNo05jCB"
      },
      "outputs": [],
      "source": [
        "# Notice the use of `tf.function`\n",
        "# This annotation causes the function to be \"compiled\".\n",
        "@tf.function\n",
        "def train_step(images):\n",
        "    noise = tf.random.normal([BATCH_SIZE, noise_dim])\n",
        "\n",
        "    with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:\n",
        "      generated_images = generator(noise, training=True)\n",
        "\n",
        "      real_output = discriminator(images, training=True)\n",
        "      fake_output = discriminator(generated_images, training=True)\n",
        "\n",
        "      gen_loss = generator_loss(fake_output)\n",
        "      disc_loss = discriminator_loss(real_output, fake_output)\n",
        "\n",
        "    gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)\n",
        "    gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)\n",
        "\n",
        "    generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))\n",
        "    discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "2M7LmLtGEMQJ"
      },
      "outputs": [],
      "source": [
        "def train(dataset, epochs):\n",
        "  for epoch in range(epochs):\n",
        "    start = time.time()\n",
        "\n",
        "    for image_batch in dataset:\n",
        "      train_step(image_batch)\n",
        "\n",
        "    # Produce images for the GIF as we go\n",
        "    display.clear_output(wait=True)\n",
        "    generate_and_save_images(generator,\n",
        "                             epoch + 1,\n",
        "                             seed)\n",
        "\n",
        "    # Save the model every 15 epochs\n",
        "    if (epoch + 1) % 15 == 0:\n",
        "      checkpoint.save(file_prefix = checkpoint_prefix)\n",
        "\n",
        "    print ('Time for epoch {} is {} sec'.format(epoch + 1, time.time()-start))\n",
        "\n",
        "  # Generate after the final epoch\n",
        "  display.clear_output(wait=True)\n",
        "  generate_and_save_images(generator,\n",
        "                           epochs,\n",
        "                           seed)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "2aFF7Hk3XdeW"
      },
      "source": [
        "**Generate and save images**\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "RmdVsmvhPxyy"
      },
      "outputs": [],
      "source": [
        "def generate_and_save_images(model, epoch, test_input):\n",
        "  # Notice `training` is set to False.\n",
        "  # This is so all layers run in inference mode (batchnorm).\n",
        "  predictions = model(test_input, training=False)\n",
        "\n",
        "  fig = plt.figure(figsize=(4,4))\n",
        "\n",
        "  for i in range(predictions.shape[0]):\n",
        "      plt.subplot(4, 4, i+1)\n",
        "      plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')\n",
        "      plt.axis('off')\n",
        "\n",
        "  plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))\n",
        "  plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "dZrd4CdjR-Fp"
      },
      "source": [
        "## Train the model\n",
        "Call the `train()` method defined above to train the generator and discriminator simultaneously. Note, training GANs can be tricky. It's important that the generator and discriminator do not overpower each other (e.g., that they train at a similar rate).\n",
        "\n",
        "At the beginning of the training, the generated images look like random noise. As training progresses, the generated digits will look increasingly real. After about 50 epochs, they resemble MNIST digits. This may take about one minute / epoch with the default settings on Colab."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 302
        },
        "colab_type": "code",
        "executionInfo": {
          "elapsed": 199,
          "status": "ok",
          "timestamp": 1554225743725,
          
          "user_tz": 420
        },
        "id": "Ly3UN0SLLY2l",
        "outputId": "ee38220a-b9e4-4efa-f9a5-646667320c8d"
      },
      "outputs": [
        {
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "\u003cFigure size 288x288 with 16 Axes\u003e"
            ]
          },
          "metadata": {
            "tags": []
          },
          "output_type": "display_data"
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "CPU times: user 3min 10s, sys: 48.1 s, total: 3min 58s\n",
            "Wall time: 10min 52s\n"
          ]
        }
      ],
      "source": [
        "%%time\n",
        "train(train_dataset, EPOCHS)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "rfM4YcPVPkNO"
      },
      "source": [
        "Restore the latest checkpoint."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "colab_type": "code",
        "executionInfo": {
          "elapsed": 702406,
          "status": "ok",
          "timestamp": 1554225743900,
          
          "user_tz": 420
        },
        "id": "XhXsd0srPo8c",
        "outputId": "ab3fb7e2-c445-4d1a-bf7f-7761891f0058"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "\u003ctensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f1840619d30\u003e"
            ]
          },
          "execution_count": 26,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "P4M_vIbUi7c0"
      },
      "source": [
        "## Create a GIF\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "WfO5wCdclHGL"
      },
      "outputs": [],
      "source": [
        "# Display a single image using the epoch number\n",
        "def display_image(epoch_no):\n",
        "  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 305
        },
        "colab_type": "code",
        "executionInfo": {
          "elapsed": 702651,
          "status": "ok",
          "timestamp": 1554225744157,
          
          "user_tz": 420
        },
        "id": "5x3q9_Oe5q0A",
        "outputId": "b37b81c4-3648-44b9-ac25-d8124600ad23"
      },
      "outputs": [
        {
          "data": {
            "image/png": "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\nYII=\n",
            "text/plain": [
              "\u003cPIL.PngImagePlugin.PngImageFile image mode=RGBA size=288x288 at 0x7F18400E3A58\u003e"
            ]
          },
          "execution_count": 28,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "display_image(EPOCHS)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "NywiH3nL8guF"
      },
      "source": [
        "Use `imageio` to create an animated gif using the images saved during training."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "IGKQgENQ8lEI"
      },
      "outputs": [],
      "source": [
        "anim_file = 'dcgan.gif'\n",
        "\n",
        "with imageio.get_writer(anim_file, mode='I') as writer:\n",
        "  filenames = glob.glob('image*.png')\n",
        "  filenames = sorted(filenames)\n",
        "  last = -1\n",
        "  for i,filename in enumerate(filenames):\n",
        "    frame = 2*(i**0.5)\n",
        "    if round(frame) \u003e round(last):\n",
        "      last = frame\n",
        "    else:\n",
        "      continue\n",
        "    image = imageio.imread(filename)\n",
        "    writer.append_data(image)\n",
        "  image = imageio.imread(filename)\n",
        "  writer.append_data(image)\n",
        "\n",
        "import IPython\n",
        "if IPython.version_info \u003e (6,2,0,''):\n",
        "  display.Image(filename=anim_file)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "cGhC3-fMWSwl"
      },
      "source": [
        "If you're working in Colab you can download the animation with the code below:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "uV0yiKpzNP1b"
      },
      "outputs": [],
      "source": [
        "try:\n",
        "  from google.colab import files\n",
        "except ImportError:\n",
        "  pass\n",
        "else:\n",
        "  files.download(anim_file)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "k6qC-SbjK0yW"
      },
      "source": [
        "## Next steps\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "xjjkT9KAK6H7"
      },
      "source": [
        "This tutorial has shown the complete code necessary to write and train a GAN. As a next step, you might like to experiment with a different dataset, for example the Large-scale Celeb Faces Attributes (CelebA) dataset [available on Kaggle](https://www.kaggle.com/jessicali9530/celeba-dataset/home). To learn more about GANs we recommend the [NIPS 2016 Tutorial: Generative Adversarial Networks](https://arxiv.org/abs/1701.00160).\n"
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [],
      "name": "dcgan.ipynb",
      "provenance": [],
      "toc_visible": true,
      "version": "0.3.2"
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
